package com.program.beautymakeup.aop;

import com.alibaba.fastjson.JSON;
import com.program.beautymakeup.common.AuthCheck;
import com.program.beautymakeup.domain.entity.User;
import com.program.beautymakeup.utils.JwtUtil;
import com.program.beautymakeup.utils.RequestUtil;
import com.program.beautymakeup.utils.SecurityUtil;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * @author CaiKe
 * @date 2023/2/8 19:54
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class AuthCheckAOP {

    private final AppProperties appProperties;
    private final JwtUtil jwtUtil;

    /**
     * 权限校验切入点
     */
    @Pointcut("@annotation(com.program.beautymakeup.common.AuthCheck)")
    public void AuthPointCut() {
    }

    @Before(value = "AuthPointCut()")
    public void DoAuthCheck(JoinPoint joinPoint) {
        log.info("in auth check.");
        // 尝试获取Token，能拿到即已登录
        validateToken();
        final Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 校验权限
        final AuthCheck authAnno = method.getAnnotation(AuthCheck.class);
        final int need = authAnno.value();
        if (need != -1) {
            if (need < SecurityUtil.getCurrentUser().getRole())
                throw new UserAuthException("用户权限不够！需要" + need + "级权限");
        }
    }

    /**
     * 检测用户是否登录
     *
     * @return
     */
    private void validateToken() {
        final String token = RequestUtil.getCurrentRequest().getHeader(appProperties.getJwt().getHeader());
        if (Strings.isEmpty(token))
            throw new UserAuthException("未检测到有效Token");
        // 解析Token，获取JSON化的User，存入Session中
        var claimsOpt = Optional.ofNullable((Claims) null);
        claimsOpt = decodeJWTToken(token);
        claimsOpt.ifPresentOrElse(claims -> {
                    final User user;
                    try {
                        user = mapToUser((Map) claims.get("user"));
                    } catch (Exception e) {
                        throw new UserAuthException("Token 无效!");
                    }
                    RequestUtil.getCurrentRequest().getSession().setAttribute("user", user);
                },
                () -> {
                    throw new UserAuthException("Token 无效!");
                });
//        claimsOpt.filter(claims -> Objects.nonNull(claims.get("user")))
//                .ifPresentOrElse(
//                        claims -> RequestUtil.getCurrentRequest().getSession().setAttribute("user", claims.get("user"))
//                        ,
//                        () -> {
//                            throw new UserAuthException("Token 无效!");
//                        });
    }


    /**
     * 获取 Token 中的Claims中保存的信息，无则返回 empty()
     */
    private Optional<Claims> decodeJWTToken(String token) {
        try {
            return Optional.of(Jwts.parserBuilder().setSigningKey(jwtUtil.getKey()).build().parseClaimsJws(token).getBody());
        } catch (ExpiredJwtException | SignatureException | MalformedJwtException | UnsupportedJwtException |
                 IllegalArgumentException e) {
            return Optional.empty();
        }
    }

    /**
     * 把source转为target
     * @param source source
     * @return 返回值
     * @throws Exception newInstance可能会抛出的异常
     */
    private static User mapToUser(Map source) throws Exception {
        Field[] fields = User.class.getDeclaredFields();
        User user = new User();
        for(Field field:fields){
            Object val;
            if((val=source.get(field.getName()))!=null){
                field.setAccessible(true);
                field.set(user,val);
            }
        }
        return user;
    }

}